<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>

        img:hover {
            opacity: 0.2;
            transition: all 1.0s;
        }

        li {
            list-style: none;
        }

        pre,
        ul.sub {
            padding: 4px;
            border-radius: 4px;
            display: block;
            margin-right: 40px;
            background-color: #efefef;
        }

    </style>
</head>
<body>
<h2 style="width: 100%; text-align: center">Launcher 启动应用流程分析及页面加载更新流程分析</h2>
<h2>应用启动流程</h2>
<ul>
    <li>
        <h3>查找onClick</h3>

        <div>启动应用过程是由用户触发，用户点击Enter按键的时候会启动应用，因此查找搜索onClick方法；</div>
    <pre>
    this.state.apps.forEach(function(app, index) {
      // 隐藏应用，比如homescreen, system应用
      if (app.shouldHide()) {
        // ...
        return;
      }
      //zmm@tcl.com Add unread sms and missed call to app icon
      var unread = '';
      // 设置未读消息数量
      if (app.entry == 'dialer' ||
        app.manifestURL == 'app://sms.gaiamobile.org/manifest.webapp') {
        var ref = app.entry ? 'dialerunread' : 'smsunread';
        unread = < div id="unread" className="hidden" ref={ref}>< /div>;;
      }
      //end
      row.push(
                // 应用图标
                < div key={app.uniName}
                    data-col-index={count % 3}
                    data-manifest-url={app.manifestURL}
                    data-entry={app.entry || ''}
                    data-category={app.category || ''}
                    // 是否是Favorite应用
                    data-favorite={app.isFavorite ? 'true' : 'false'}
                    // 调用app.launch方法启动应用
                    onClick={(e) => {app.launch()}}>
                // 未读消息提示
                < div className="icon" style={style} >
                    {/*zmm@tcl.com Add unread sms and missed call to app icon*/}
                    {unread}
                < /div>
                // 应用名称
                < div className="name">
                    {app.name}
                < /div>
        </div>);
    </pre>
    </li>
    <li>
        <h3>state.apps</h3>
        <pre>
  import AppStore from 'app_store';

  componentDidMount() {
    this.element = ReactDOM.findDOMNode(this);
    this.store = AppStore;
    // 监听ready事件，当apps加载完成时改变state，react会自动刷新列表；
    AppStore.on('ready', () => {
      this.setState({
        // 数据来源为AppStore.apps
        apps: AppStore.apps
      });
    });
    AppStore.on('change', () => {
      this.setState({
        // 监听apps改变事件
        apps: AppStore.apps
      });
      this.focusIfPossible();
    });
    this.setState({
      apps: AppStore.apps
    });
  }
        </pre>
        <div>state.apps数据来自于AppStore，并且通过监听change事件，如果AppStore中的apps发生了变化，或者是语言设置改变了，都会自动刷新launcher中的应用程序列表；</div>
    </li>
    <li>
        <h3>在AppStore中查找apps，找出对apps进行修改操作的地方</h3>

        <div>addApp(app, mozApp)</div>
        <pre>
  addApp(app, mozApp) {

    var icon = '';
    var max = 0;
    var origin = app.origin;
    var icons = app.manifest.icons;
    var _allOrder = this.PREDEFINED_ORDER;
    var _allCategory = this.PREDEFINED_CATEGORY;

    // set the max icon
    app.icon_url = icon;
    // app.launch方法：通过调用mozApp.launch方法来启动应用；
    app.launch = () => {
      window.performance.mark('appLaunch@' + app.origin);
      // contacts
      // 启动应用
      mozApp.launch(app.entry);
    }
    // 是否隐藏应用
    app.shouldHide = () => {
      return app.manifest.role === 'system' || app.manifest.role === 'input' ||
        app.manifest.role === 'theme' || app.manifest.role === 'homescreen' ||
        app.entry === 'call_log';
    }
    app.mozApp = mozApp;
    app.uniName = this.getAppUniName(app);
    // ...

    app.theme = app.manifest.theme_color || null;
    // push 添加到apps数组中；
    this.apps.push(app);
  };
        </pre>
    </li>
    <li>
        <h3>查找调用了addApp的地方</h3>

        <div>parse(mozApp)</div>
        <pre>
  parse(mozApp) {
    var app;
    // the manifest info of webapp with downloaded manifest is in mozApp.updateManifest
    var manifest = mozApp.manifest || mozApp.updateManifest;
    // Entry point app
    var entryPoints = manifest.entry_points;
    if (entryPoints) {
      var name = '';
      // Do deep copy to avoid reference to be overwritten only when we're entry points.
      manifest = JSON.parse(JSON.stringify(manifest));

      for (var ep in entryPoints) {
        var app = {};
        // ...
        for (var key in mozApp) {
          app[key] = mozApp[key];
        }
        app.manifest = manifest;
        app.name = name;
        app.entry = ep;
        // 添加app
        this.addApp(app, mozApp);
      }
    } else {
      app = {};
      // 拷贝mozApp的属性赋值给app；
      for (var key in mozApp) {
        app[key] = mozApp[key];
      }
      app.name = new ManifestHelper(app.manifest).name;
      // 添加app
      this.addApp(app, mozApp);
    }
  }
        </pre>
        <div>拷贝mozApp的属性给app对象，并且给app添加一些特殊的属性，但app和mozApp实际上是完全不同类型的对象;</div>
    </li>
    <li>
        <h3>updateMgmt()</h3>
        <pre>
  updateMgmt() {
    let self = this;
    navigator.mozApps.mgmt.getAll().onsuccess = function mozAppGotAll(evt) {
      var apps = evt.target.result;
      apps.forEach(function(mozApp) {
        // 使用parse方法解析mozApp, 并且添加到apps数组中；
        self.parse(mozApp);
      });

      FavoriteStore.start();

      self.ready = true;
      // 分发ready事件，
      self.emit('ready');
    };
  }
        </pre>
    </li>
    <li>
        <h3>start()</h3>
        <pre>
  start() {
    if (!navigator.mozApps.mgmt) {
      return;
    }
    this.fs = FavoriteStore;
    this.firstTime = true;
    FavoriteStore.on('change', () => {
      if (this.firstTime) {
        this.firstTime = false;
        this.update();
        this.sort();
      } else {
        this.update();
      }
    });
    // 更新apps列表
    this.updateMgmt();
    window.addEventListener('localized', this);

    // 注册事件 for re-run this.start() when app-installed & app-uninstalled
    navigator.mozApps.mgmt.addEventListener('install', this);
    navigator.mozApps.mgmt.addEventListener('uninstall', this);
    navigator.mozApps.mgmt.addEventListener('enabledstatechange', this);
  }
// 初始化一个AppStore实例；AppStore为单例！！！
var appStore = new AppStore();
appStore.start();
export default appStore;
        </pre>
    </li>
    <li>
        <h3>再说mozApp和app</h3>

        <div>mozApp 通过navigator.mozApps.mgmt.getAll()方法获取得到，数据类型为DOMApplication</div>
        <pre>
DOMApplication { manifest: Object, updateManifest: null, manifestURL: "app://bluetooth.gaiamobile.org/manifest…", origin: "app://bluetooth.gaiamobile.org",
installOrigin: "app://bluetooth.gaiamobile.org", installTime: 1488738041733, removable: false, enabled: true, receipts: Array[0], progress: 0 }
        </pre>
        <div>app为普通的javascript对象</div>
        <pre>
Object { download: download(), cancelDownload: cancelDownload(), launch: addApp/app.launch(), clearBrowserData: clearBrowserData(),
checkForUpdate: checkForUpdate(), connect: connect(), getConnections: getConnections(),
addReceipt: addReceipt(), removeReceipt: removeReceipt(), replaceReceipt: replaceReceipt(), 36 more… }
launch: addApp/app.launch()
        </pre>
    </li>
    <li>
        <h3>change事件分发</h3>

        <div>removeApp(app)</div>
        <pre>
  removeApp(app) {
    let targetAppUniName = this.getAppUniName(app);
    this.apps.some((app, index) => {
      if (app.uniName === targetAppUniName) {
        this.apps.splice(index, 1);
        // 分发change事件
        this.emit('change');
        return true;
      } else {
        return false;
      }
    });
  };
        </pre>
        <div>install(app)</div>
        <pre>
  _handle_install(evt) {
    console.log(evt.application);
    this.parse(evt.application);
    // 分发change事件
    this.emit('change');
  }
        </pre>
        <div>_handle_localized，本地语言发生改变</div>
        <pre>
  _handle_localized() {
    this.apps.forEach((app) => {
      let _manifestObj = app.entry ? app.manifest.entry_points[app.entry] : app.manifest;
      app.name = new ManifestHelper(_manifestObj).name;
    });
    // 分发change事件
    this.emit('change');
  }
}
        </pre>
    </li>
    <li>
        <h3>事件分发this.emit</h3>
        <pre>
// AppStore
class AppStore extends BaseModule {
...
}

// BaseModule
import BaseEmitter from 'base-emitter';
...
export default class BaseModule extends BaseEmitter {
...
}
        </pre>
        <div>事件分发及监听，使用Node.js中的更加高效的EventEmitter事件循环模块，而非window.addEventListener;</div>
    </li>
</ul>
<h2>launcher页面加载及更新流程</h2>
<a href="launcher.pdf">Launcher页面加载及更新流程</a>
</body>
</html>